package redisclient

import (
	"gitee.com/lsy007/redisclient/model"
	"github.com/pquerna/ffjson/ffjson"
	"strconv"
)

// GeoAdd(key string, locations []*GeoLocations...) (count int64, err error)
// GeoPos(key string, members ...string) (geoPos []*GeoPos, err error)
// GeoDist(key string, member1, member2 string, unit string) (dist float64, err error)
// GeoRadiusStore(key string, longitude, latitude  float64, query *GeoRadiusQuery) (count int64, err error)
// GeoRadiusByMemberStore(key string, member string, query *GeoRadiusQuery) (count int64, err error)
// GeoHash(key string, members ...string) (hashes []string, err error)

// GeoLocation is used with GeoAdd to add geospatial location.
// 有效的经度介于 -180 度至 180 度之间。
// 有效的纬度介于 -85.05112878 度至 85.05112878 度之间
type GeoLocation struct {
	Name      string
	Longitude float64
	Latitude  float64
}

// GeoRadiusQuery is used with GeoRadius to query geospatial index.
type GeoRadiusQuery struct {
	Radius    float64
	Unit      string // Can be m, km, ft, or mi. Default is km.
	Count     int64
	Sort      string // Can be ASC or DESC. Default is no sort order.
	WithCoord bool   // GeoRadius and GeoRadiusByMember 使用
	WithDist  bool   // GeoRadius and GeoRadiusByMember 使用
	Store     string // GeoRadius 类操作获取结果保存在指定的 Store 键值中，存储的有序集合分数值为获取到的对象的经纬度信息
	StoreDist string // GeoRadius 类操作获取结果保存在指定的 StoreDist 键值中，存储的有序集合分数值为获取到的对象与指定经纬度或者指定对象的距离
}

// 用于接收获取多个元素的经纬度信息结构
type GeoPosition struct {
	Longitude, Latitude float64
}

// 用于接收 GeoRadius 和 GeoRadiusByMember 两个方法的结果值
type GeoRadiusPosition struct {
	Name     string
	Dist     float64
	Position GeoPosition
}

// 将给定的空间元素（纬度、经度、名字）添加到指定的键里面，返回新添加到键里面的空间元素数量， 不包括那些已经存在但是被更新的元素
func (c *RedisClient) GeoAdd(key string, geoLocations ...*GeoLocation) (count int64, err error) {
	locations := make([]*model.GeoLocation, 0)
	for _, v := range geoLocations {
		locations = append(locations, &model.GeoLocation{
			Name:      v.Name,
			Longitude: v.Longitude,
			Latitude:  v.Latitude,
		})
	}
	args := &model.RedisArgsModel{Key: key, GeoLocations: locations}
	response, err = c.Request("GeoAdd", "Geo", args)
	return response.Int, err
}

// 从键里面返回所有给定位置元素的位置（经度和纬度）,返回一个数组， 数组中的每个项都由两个元素组成，第一个是精度，第二个是纬度
func (c *RedisClient) GeoPos(key string, members ...string) (geoPos []*GeoPosition, err error) {
	args := &model.RedisArgsModel{Key: key, Names: members}
	response, err = c.Request("GeoPos", "Geo", args)

	geoPos = make([]*GeoPosition, 0)
	err = ffjson.Unmarshal(response.Bytes, &geoPos)
	return
}

// 返回两个给定位置之间的距离，如果两个位置之间的其中一个不存在， 那么命令返回空值
// unit 取值范围：m-米/km-千米/mi-英里/ft-英尺
func (c *RedisClient) GeoDist(key string, member1, member2 string, unit string) (dist float64, err error) {
	args := &model.RedisArgsModel{Key: key, Names: []string{member1, member2}, Unit: unit}
	response, err = c.Request("GeoDist", "Geo", args)
	return response.Float, err
}

// 以给定的经纬度为中心，查询在指定范围内的所有元素并返回
func (c *RedisClient) GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) (result []GeoRadiusPosition, err error) {
	args := &model.RedisArgsModel{Key: key, Longitude: longitude, Latitude: latitude, GeoRadiusQuery: &model.GeoRadiusQuery{
		Radius:    query.Radius,
		Unit:      query.Unit,
		Count:     query.Count,
		Sort:      query.Sort,
		WithCoord: query.WithCoord,
		WithDist:  query.WithDist,
	}}
	response, err = c.Request("GeoRadius", "Geo", args)
	return byBytesGetGeoPosition(response.Bytes, query)
}

// 以指定元素为中点，找出在指定范围内的所有元素并返回
func (c *RedisClient) GeoRadiusByMember(key, member string, query *GeoRadiusQuery) (result []GeoRadiusPosition, err error) {
	args := &model.RedisArgsModel{Key: key, Name: member, GeoRadiusQuery: &model.GeoRadiusQuery{
		Radius:    query.Radius,
		Unit:      query.Unit,
		Count:     query.Count,
		Sort:      query.Sort,
		WithCoord: query.WithCoord,
		WithDist:  query.WithDist,
	}}
	response, err = c.Request("GeoRadiusByMember", "Geo", args)
	return byBytesGetGeoPosition(response.Bytes, query)
}

// 以给定的经纬度为中心，查询在指定范围内的所有元素，存储在指定的键值中，且返回元素数量
// GeoRadiusQuery 结构中，Store 和 StoreDist 必填其一
func (c *RedisClient) GeoRadiusStore(key string, longitude, latitude float64, query *GeoRadiusQuery) (count int64, err error) {
	args := &model.RedisArgsModel{Key: key, Longitude: longitude, Latitude: latitude, GeoRadiusQuery: &model.GeoRadiusQuery{
		Radius:    query.Radius,
		Unit:      query.Unit,
		Count:     query.Count,
		Sort:      query.Sort,
		Store:     query.Store,
		StoreDist: query.StoreDist,
	}}
	response, err = c.Request("GeoRadiusStore", "Geo", args)
	return response.Int, err
}

// 以指定元素为中点，找出在指定范围内的所有元素，存储在指定的键值中，且返回元素数量
// GeoRadiusQuery 结构中，Store 和 StoreDist 必填其一
func (c *RedisClient) GeoRadiusByMemberStore(key, member string, query *GeoRadiusQuery) (count int64, err error) {
	args := &model.RedisArgsModel{Key: key, Name: member, GeoRadiusQuery: &model.GeoRadiusQuery{
		Radius:    query.Radius,
		Unit:      query.Unit,
		Count:     query.Count,
		Sort:      query.Sort,
		Store:     query.Store,
		StoreDist: query.StoreDist,
	}}
	response, err = c.Request("GeoRadiusByMemberStore", "Geo", args)
	return response.Int, err
}

// 返回一个或多个位置元素的 Geohash 表示
func (c *RedisClient) GeoHash(key string, members ...string) (hashes []string, err error) {
	args := &model.RedisArgsModel{Key: key, Names: members}
	response, err = c.Request("GeoHash", "Geo", args)
	return response.StringSlice, err
}

func byBytesGetGeoPosition(bytes []byte, query *GeoRadiusQuery) (result []GeoRadiusPosition, err error) {
	// 1. 判断字节数组是否存在信息
	if bytes == nil {
		return
	}
	// 2. 判断是否类型
	var dataType int
	if query.WithCoord && query.WithDist {
		dataType = 3 // 距离和坐标信息都要获取
	} else if query.WithDist {
		dataType = 2 // 只获取距离
	} else if query.WithCoord {
		dataType = 1 // 只获取坐标信息
	} else {
		return getNames(bytes)
	}
	// 3. 解析字节切片为二维指针切片
	list := make([][]interface{}, 0)
	if err = ffjson.Unmarshal(bytes, &list); err != nil {
		return
	}
	// 4. 解析数据
	result = make([]GeoRadiusPosition, 0)
	for _, v := range list {
		var position GeoRadiusPosition
		position.Name = v[0].(string)
		switch dataType {
		case 1:
			pos := v[1].([]interface{})
			if position.Position.Longitude, err = strconv.ParseFloat(pos[0].(string), 64); err != nil {
				return
			}
			if position.Position.Longitude, err = strconv.ParseFloat(pos[0].(string), 64); err != nil {
				return
			}
		case 2:
			if position.Dist, err = strconv.ParseFloat(v[1].(string), 64); err != nil {
				return
			}
		case 3:
			// 获取离中心点的距离信息
			if position.Dist, err = strconv.ParseFloat(v[1].(string), 64); err != nil {
				return
			}
			// 获取当前元素的坐标信息
			pos := v[2].([]interface{})
			if position.Position.Longitude, err = strconv.ParseFloat(pos[0].(string), 64); err != nil {
				return
			}
			if position.Position.Longitude, err = strconv.ParseFloat(pos[0].(string), 64); err != nil {
				return
			}
		}
		result = append(result, position)
	}
	return
}

func getNames(bytes []byte) (result []GeoRadiusPosition, err error) {
	list := make([]string, 0)
	if err = ffjson.Unmarshal(bytes, &list); err != nil {
		return
	}
	result = make([]GeoRadiusPosition, 0)
	for _, name := range list {
		result = append(result, GeoRadiusPosition{Name: name})
	}
	return
}
